package com.katze.common.http;

import com.katze.common.data.DataProperties;
import com.katze.common.env.AppEnvironment;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.RequestFacade;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;

public class HttpUtils {
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    private static final String[] headers = {"Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR", "X-Real-IP"};

    private static final String IP_REGION_FILE = "/ip2region.xdb";

    private static byte[] buffer;

    static {
        Path file = AppEnvironment.RESOURCES.getPath("ip2region.xdb");
        if (Files.notExists(file)) {
            URL url = Optional.ofNullable(HttpUtils.class.getResource(IP_REGION_FILE))
                    .orElseGet(() -> HttpUtils.class.getClassLoader().getResource(IP_REGION_FILE));
            try {
                buffer = IOUtils.toByteArray(Objects.requireNonNull(url));
                log.info("ip2region.xdb资源文件已加载");
            } catch (Exception e) {
                log.error("{}资源文件加载失败, 将无法获取用户登录地址", url, e);
            }
        } else {
            try {
                buffer = Files.readAllBytes(file);
                log.info("ip2region.xdb资源文件已加载");
            } catch (IOException e) {
                log.error("{}资源文件加载失败, 将无法获取用户登录地址", file, e);
            }
        }
    }

    /**
     * 获取用户真实IP地址，不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值
     *
     * @return ip
     */
    public static String getRemoteAddress() {
        return getRemoteAddress(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
    }

    @Deprecated
    public static String getRemoteAddress(RequestAttributes request) {
        return getRemoteAddress(((ServletRequestAttributes) request).getRequest());
    }

    public static String getRemoteAddress(HttpServletRequest request) {
        // request 有可能会变成ShiroHttpServletRequest，如果为该类型时request对象为空
        if (request instanceof RequestFacade) {
            String ip = request.getHeader("x-forwarded-for");
            if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
                // 多次反向代理后会有多个ip值，第一个ip才是真实ip
                if (ip.contains(",")) ip = ip.split(",")[0];
            }
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                for (String header : headers) {
                    ip = request.getHeader(header);
                    if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) break;
                }
            }
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
        } else {
            return "127.0.0.1";
        }
    }

    /**
     * 从多级反向代理中获得第一个非unknown IP地址
     *
     * @param ip 获得的IP地址
     * @return 第一个非unknown IP地址
     */
    public static String getMultistageReverseProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (!(StringUtils.isBlank(subIp) || "unknown".equalsIgnoreCase(subIp))) {
                    ip = subIp;
                    break;
                }
            }
        }
        return StringUtils.substring(ip, 0, 255);
    }

    public static void searchRegion(HttpServletRequest request, BiConsumer<String, String> rs) {
        String address = getRemoteAddress(request);
        String region = "";
        try (IPSearcher searcher = IPSearcher.build(buffer)){
            region = searcher.search(address);
        } catch (Exception e) {
            log.error("获取地址信息异常", e);
        } finally {
            rs.accept(address, region);
        }
    }

    /**
     * 读取请求中携带的所有参数
     * @param request 请求对象
     * @return Map<String, Object>
     */
    public static DataProperties toProperties(ServletRequest request) {
        Validate.notNull(request, "Request must not be null");
        DataProperties params = new DataProperties();
        Optional.ofNullable(request.getParameterNames()).ifPresent(item ->{
            while(item.hasMoreElements()) {
                String paramName = item.nextElement();
                String[] values = request.getParameterValues(paramName);
                if(values != null && values.length != 0) {
                    params.put(paramName, values.length == 1? values[0]:values);
                }
            }
        });
        return params;
    }

    /**
     * 将请求中的参数转换对象
     */
    public static  <T>T toBean(HttpServletRequest request, Class<T> clazz) {
        T o = null;
        try {
            o = clazz.getDeclaredConstructor().newInstance();
            Function<String, Field> func = (name) -> {
                Field field = null;
                try {
                    field = clazz.getSuperclass().getDeclaredField(name);
                } catch (NoSuchFieldException e) {
                    try {
                        field = clazz.getDeclaredField(name);
                    } catch (NoSuchFieldException ignored) {}
                }
                return field;
            };
            Enumeration<String> enumeration = request.getParameterNames();
            while (enumeration.hasMoreElements()) {
                Field field = func.apply(enumeration.nextElement());
                if (field != null) {
                    if(Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) continue;
                    field.setAccessible(true);
                    Object val = request.getParameter(field.getName());
                    // 当字段类型时集合类型且数据是字符串类型时，需特殊处理
                    if(field.getType().isAssignableFrom(List.class)){
                        if(val != null) field.set(o, Arrays.asList(val.toString().split(",", -1)));
                    } else if (field.getType().isAssignableFrom(Integer.class) || field.getType().isAssignableFrom(int.class)) {
                        if(val != null) field.set(o, Integer.parseInt(val.toString()));
                    }else if (field.getType().isAssignableFrom(Long.class) || field.getType().isAssignableFrom(long.class)) {
                        if(val != null) field.set(o, Long.parseLong(val.toString()));
                    }else if (field.getType().isAssignableFrom(Double.class) || field.getType().isAssignableFrom(double.class)) {
                        if(val != null) field.set(o, Double.parseDouble(val.toString()));
                    }else if (field.getType().isAssignableFrom(Boolean.class) || field.getType().isAssignableFrom(boolean.class)) {
                        if(val != null) field.set(o, Boolean.parseBoolean(val.toString()));
                    }else {
                        field.set(o, val);
                    }
                }
            }
        }catch (Exception e){
            log.error("参数转换对象出现异常",e);
        }
        return o;
    }

    public static ResponseDownloadHelper download (HttpServletResponse response) {
        return new ResponseDownloadHelper(response);
    }
}
